package jwtsession

import (
	"context"
	"crypto/rand"
	"encoding/base32"
	"errors"
	"io"
	"net/http"
	"strings"
)

type sessionIdKey struct{}

type Session struct {
	ID      string
	Values  map[string]string
	Options *Options
	IsNew   bool
	store   Store
	name    string
}

func (s *Session) Save(ctx context.Context, writter WritterFunc) error {
	if s.store == nil {
		return errors.New("can't save with empty store")
	}
	err := s.store.Save(ctx, s.ID, s.Values, s.Options)
	if err != nil {
		return err
	}

	//inspector 写cookie 或者 jwt
	if s.Options.Inspector != nil {
		s.Options.Inspector.Write(ctx, s.name, s.ID, s.Options, writter)
	}

	return nil
}

func (s *Session) Add(key string, val string) error {
	s.Values[key] = val
	return nil
}

func (s *Session) Del(key string) error {
	delete(s.Values, key)
	return nil
}

func (s *Session) Clear(key string) error {
	s.Values = make(map[string]string)
	return nil
}

func (s *Session) Delete() error {
	s.Options.MaxAge = 0
	return nil
}

func (s *Session) Get(key string) (string, error) {
	val, ok := s.Values[key]
	if !ok {
		return "", &NotFoundError{}
	}
	return val, nil
}

func (s *Session) Name() string {
	return s.name
}

func (s *Session) genId() string {
	if s.ID == "" {
		s.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(generateRandomKey(32)), "=")
	}
	return s.ID
}

// Store returns the session store used to register the session.
func (s *Session) Store() Store {
	return s.store
}

func generateRandomKey(length int) []byte {
	k := make([]byte, length)
	if _, err := io.ReadFull(rand.Reader, k); err != nil {
		return nil
	}
	return k
}

func AutoSession(ctx context.Context, store Store, req *http.Request, name string, handlerOpts ...HandlerOption) *Session {
	opts := &Options{
		Path:     "/",
		Domain:   "",
		MaxAge:   24 * 3600,
		Secure:   false,
		HttpOnly: false,
		SameSite: http.SameSiteDefaultMode,
		// 上面都是 cookie 的选项
		Inspector: CookieInspector{},
	}
	for _, o := range handlerOpts {
		o(opts)
	}
	isNew := false

	// 先从 registry 里获取
	regSession, err := GetRegistry(req).Get(name)
	if err == nil && regSession != nil {
		regSession.Options = opts
		regSession.IsNew = isNew
		return regSession
	}

	// 初始化session
	newSession := &Session{
		IsNew:   true,
		Options: opts,
		Values:  map[string]string{},
		store:   store,
		name:    name,
	}
	// 从请求获取 sessionId
	sessionId, err := opts.Inspector.Read(ctx, req, name)
	if err == nil && sessionId != "" {
		storeValues, err := store.Get(ctx, sessionId)
		if err == nil {
			newSession.ID = sessionId
			for k, v := range storeValues {
				newSession.Values[k] = v
			}
			GetRegistry(req).Set(name, newSession)
			return newSession
		}
	}

	//从context里读取
	newSession.genId()
	GetRegistry(req).Set(name, newSession)
	return newSession
}
